using System;
using System.Collections;
using System.Linq;
using Headsight.BPMN2WF.BPMN.Model;
using Headsight.BPMN2WF.BPMN.Schemas;
using Headsight.BPMN2WF.Testing.TestObjCtors;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Headsight.BPMN2WF.BPMN.BlockModel
{
    [TestClass]
    public class BPMNProcessBlockTest : BPMNBaseElementTestFixture<BPMNFlowNodesBlock, tFlowNode>
    {
        private BPMNFlowNode _nodeIn;
        private BPMNFlowNode _node;

        protected override tFlowNode CreateTElement()
        {
            return new tFlowNodeCtor().Create();
        }

        protected override BPMNFlowNodesBlock CreateSut(tFlowNode tProcess, string expectedId)
        {
            _nodeIn = new BPMNFlowNodeCtor().WithId("_id_start").Create();
            _node = new BPMNFlowNodeCtor().WithId("_id_end").Create();

            tProcess.id = expectedId;

            return new BPMNFlowNodesBlock(new BPMNFlowNodeCtor().WithTFlowElement(tProcess).WithId(expectedId).Create());
        }

        [TestMethod]
        public void NodeIn()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);

            Assert.AreSame(_nodeIn, _sut.FlowNodeIn);
        }

        [TestMethod]
        public void NodeOut_in_case_of_not_set()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);
            Assert.AreEqual(null, _sut.NodeOut);
        }

        [TestMethod]
        public void NodeOut_in_case_of_set()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);
            _sut.AddFlow(_sut.FlowNodeIn, new BPMNFlowNodeCtor().WithId("_id_**").Create());
            _sut.NodeOut = _node;

            Assert.AreSame(_node, _sut.NodeOut);
        }

        [TestMethod]
        public void GetNext_in_case_of_start_vertex_and_no_other_vertices()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);
            var actual = _sut.GetNextNodesInFlowFrom(_nodeIn);

            Assert.IsNotNull(actual);
            Assert.AreEqual(0, actual.Count);
        }

        [TestMethod]
        public void GetNext_in_case_of_NodeOut_vertex()
        {
            _sut.AddFlow(_sut.FlowNodeIn, _node);

            var actual = _sut.GetNextNodesInFlowFrom(_node);

            Assert.IsNotNull(actual);
            Assert.AreEqual(0, actual.Count);
        }

        [TestMethod]
        public void Add_GetNext()
        {
            var vert1 = new BPMNFlowNodeCtor().WithId("_id_vert1").Create();
            var vert2 = new BPMNFlowNodeCtor().WithId("_id_vert2").Create();

            _sut.AddFlow(_nodeIn, vert1);
            _sut.AddFlow(_nodeIn, vert2);
            _sut.AddFlow(vert1, _node);
            _sut.AddFlow(vert2, _node);

            var actual1 = _sut.GetNextNodesInFlowFrom(_nodeIn);
            Assert.AreEqual(2, actual1.Count);
            Assert.AreEqual(true, actual1.Any(x => x.Id == "_id_vert1"));
            Assert.AreEqual(true, actual1.Any(x => x.Id == "_id_vert2"));

            var actual2 = _sut.GetNextNodesInFlowFrom(vert1);
            Assert.AreEqual(1, actual2.Count);
            Assert.AreEqual(true, actual2.Any(x => x.Id == "_id_end"));

            var actual3 = _sut.GetNextNodesInFlowFrom(vert1);
            Assert.AreEqual(1, actual3.Count);
            Assert.AreEqual(true, actual3.Any(x => x.Id == "_id_end"));
        }

        [TestMethod]
        public void Add_is_idempotent()
        {
            _sut.AddFlow(_nodeIn, _node);
            _sut.AddFlow(_nodeIn, _node);

            var actual = _sut.GetNextNodesInFlowFrom(_nodeIn);
            Assert.AreEqual(1, actual.Count);
            Assert.AreSame(_node, actual[0]);
        }

        [TestMethod]
        public void AllNodes_initially()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);

            var actual = _sut.AllNodes;
            CollectionAssert.AreEquivalent(new[] { _nodeIn }, (ICollection)actual);
        }

        [TestMethod]
        public void AllNodes_Add()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);

            var child1 = new BPMNFlowNodeCtor().WithId("1").Create();
            var child2 = new BPMNFlowNodeCtor().WithId("2").Create();

            _sut.AddFlow(_nodeIn, child1);
            _sut.AddFlow(_nodeIn, child2);

            var actual = _sut.AllNodes;
            CollectionAssert.AreEquivalent(new[] { _nodeIn, child1, child2 }, (ICollection)actual);
        }

        [TestMethod]
        public void AllNodes_Add_same_element_multiple_times()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);

            var child1 = new BPMNFlowNodeCtor().WithId("1").Create();
            var child2 = new BPMNFlowNodeCtor().WithId("2").Create();

            _sut.AddFlow(_nodeIn, child1);
            _sut.AddFlow(child1, _nodeIn);
            _sut.AddFlow(child1, child2);

            var actual = _sut.AllNodes;
            CollectionAssert.AreEquivalent(new[] { _nodeIn, child1, child2 }, (ICollection)actual);
        }

        [TestMethod]
        public void AllNodesWithoutNodeOut_in_case_of_only_start_vertex()
        {
            var bpmnBlockGraphVertex = new BPMNFlowNodeCtor().WithId("1s1").Create();
            _sut = new BPMNFlowNodesBlock(bpmnBlockGraphVertex);

            var actual = _sut.AllNodesWithoutNodeOut;
            Assert.AreEqual(1, actual.Count);
            Assert.AreSame(bpmnBlockGraphVertex, actual[0]);
        }

        [TestMethod]
        public void AllNodesWithoutNodeOut_in_case_of_start_vertex_and_NodeOut_vertex()
        {
            var flowNode = new BPMNFlowNodeCtor().WithId("1s1").Create();

            _sut = new BPMNFlowNodesBlock(flowNode);
            _sut.NodeOut = _sut.AddFlow(_sut.FlowNodeIn, new BPMNFlowNodeCtor().WithId("1e1").Create());

            var actual = _sut.AllNodesWithoutNodeOut;
            Assert.AreEqual(1, actual.Count);
            Assert.AreSame(flowNode, actual[0]);
        }

        [TestMethod]
        public void Add_returns_last_added_node()
        {
            Assert.AreSame(_node, _sut.AddFlow(_sut.FlowNodeIn, _node));
        }

        [TestMethod]
        public void AllNodesWithoutNodeOut_in_case_of_start_vertex_and_other_non_NodeOut_vertices()
        {
            var nodeIn = new BPMNFlowNodeCtor().WithId("1s1").Create();
            var node = new BPMNFlowNodeCtor().WithId("1e1").Create();

            _sut = new BPMNFlowNodesBlock(nodeIn);
            _sut.NodeOut = _sut.AddFlow(_sut.FlowNodeIn, node);

            var actual = _sut.AllNodesWithoutNodeOut;
            Assert.AreEqual(1, actual.Count);
            Assert.AreEqual(true, actual.Contains(nodeIn));
        }

        [TestMethod]
        public void AllNodesWithoutNodeOut_in_case_of_any_element_being_blockgraph_by_its_own()
        {
            var vertStart1 = new BPMNFlowNodeCtor().WithId("1s1").Create();
            var vertStart2 = new BPMNFlowNodeCtor().WithId("1e1").Create();
            var v1 = new BPMNFlowNodeCtor().WithId("2s1").Create();
            var v2 = new BPMNFlowNodeCtor().WithId("2n").Create();


            var bpmnBlockGraph = new BPMNFlowNodesBlock(vertStart2);
            bpmnBlockGraph.AddFlow(vertStart2, v1);
            bpmnBlockGraph.AddFlow(v1, v2);

            _sut = new BPMNFlowNodesBlock(vertStart1);
            _sut.AddFlow(_sut.FlowNodeIn, bpmnBlockGraph);
            _sut.NodeOut = _sut.AddFlow(bpmnBlockGraph, new BPMNFlowNodeCtor().WithId("anyId").Create());

            var actual = _sut.AllNodesWithoutNodeOut;

            Assert.AreEqual(1 + 3, actual.Count);
            Assert.AreEqual(true, actual.Contains(vertStart1));
            Assert.AreEqual(true, actual.Contains(vertStart2));
            Assert.AreEqual(true, actual.Contains(v1));
            Assert.AreEqual(true, actual.Contains(v2));
        }

        [TestMethod]
        public void GetBPMNFlowNodeById()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);
            _sut.AddFlow(_nodeIn, _node);

            Assert.AreSame(_nodeIn, _sut.GetBPMNFlowNodeById(_nodeIn.Id));
            Assert.AreSame(_node, _sut.GetBPMNFlowNodeById(_node.Id));
        }

        [TestMethod]
        public void GetBPMNFlowNodeById_works_recursive()
        {
            _sut = new BPMNFlowNodesBlock(_nodeIn);

            var block1 = new BPMNFlowNodesBlock(_node);
            var block2 = new BPMNFlowNodesBlock(new BPMNFlowNodeCtor().WithId("4").Create());

            _sut.AddFlow(_nodeIn, block1);
            block1.AddFlow(_node, block2);

            Assert.AreSame(block1, _sut.GetBPMNFlowNodeById(block1.Id));
            Assert.AreSame(_node, _sut.GetBPMNFlowNodeById(_node.Id));
            Assert.AreSame(block2, _sut.GetBPMNFlowNodeById(block2.Id));
        }

        [TestMethod]
        public void GetBPMNFlowNodeById_in_case_of_no_such_node_exists()
        {
            Assert.AreEqual(null, _sut.GetBPMNFlowNodeById("noNodeWithSuchId"));
        }

        [TestMethod]
        public override void TestToString()
        {
            _sut = CreateSut(CreateTElement(), "*id*");
            _sut.TElement.name = "*name*";

            var act = _sut.ToString();
            const String exp = "BPMNFlowNodesBlock[Id: block_*id*, Name: *name*]";

            Assert.AreEqual(exp, act);
        }
    }
}